<?php

/***  DOCUMENTATION LAYER

Bafflegate Core Class

Name: BaffleCore
Last Update: Sep 2007
Author: Tom at klenwell@gmail.com

DESCRIPTION
	The bafflecore class serves as a foundation (an abstract of sorts) for the other
  concrete bafflegate classes that perform the practical operations of installing,
  administering, logging visitors, and reporting.
  
  It encapsulates the database object for these other classes.

METHODS
	MAGIC
	BaffleCore($debug=0, $oid=null)		*php 4 constructor*
	__construct($debug, $oid)					*php 5 constructor*
	__destruct()	
	
	PUBLIC
  select_account_data_by_domain($domain)
  get_base_domain($url)
  get_subdomain($url)
  get_url_domain($url)  
  get_path($url)
  get_query_string($url)
  
  set_exception($code, $message)
  has_exception()
  insert_exception()
  
  debug_dump($dirpath='')
  write_file($content, $fpath, $overwrite=1)
	print_d($message, $color='c33')
	print_r()
	dump()
	
	PRIVATE
  _load_dbo()
  _bake_cookie($name, $value, $duration)
  
	_set_session_data()
	_get_session_data()
	_has_session_data()
	_set_filename()
	_set_dirpath()
	
USAGE
	$Baffle = new BaffleCore($debug=1);

NOTES
	For purposes of backwards compatability, PHP 4 syntax (e.g. var declaration
	keyword rather than public and private) is used.

______________________________________________________________________________*/


// BaffleCore
/*____________________________________________________________________________*/
class BaffleCore
{
/* PUBLIC PROPERTIES */
var $debug = 0;
var $class_name = __CLASS__;
var $oid = '';
var $DS = DIRECTORY_SEPARATOR;

// encapsulated objects
var $DBO = 0;


/* PRIVATE PROPERTIES */
var $_package_root = '';
var $_config_file = '';
var $_log_dir = '';
var $_dbo_driver = '';

var $_dbu = '';
var $_dbp = '';
var $_dbn = '';
var $_dbs = '';

var $_EXCEPTION = array();
var $_DEBUG = array();

var $_filename = '';
var $_dirpath = '';


/* ** MAGIC METHODS ** */
// php4 constructor
function BaffleCore($debug=0, $oid=null)
{
	$this->__construct($debug, $oid);
	register_shutdown_function( array($this, '__destruct') );
}
// END constructor

// php5 constructor
function __construct($debug=0, $oid=null)
{
	// default
	$this->debug = $debug;
	$this->oid = ( empty($oid) ) ? $this->class_name : $oid;
	$this->_set_filename();
	$this->_set_dirpath();
	
  // path settings
  $this->_package_root = dirname(__FILE__) . $this->DS;
  $this->_config_file = $this->_package_root . 'config.inc.php';
  $this->_dbo_driver = $this->_package_root . 'oss/adodb_lite/adodb.inc.php';
  $this->_log_dir = $this->_package_root . 'log' . $this->DS;
  
  // includes
  global $CFG;
  require_once($this->_config_file);
  
  // db settings
  $this->_dbu = $CFG['db_user'];
  $this->_dbp = urlencode($CFG['db_pass']);
  $this->_dbn = $CFG['db_name'];
  $this->_dbs = $CFG['db_srvr'];
  
  // objects
  $this->_load_dbo();
	
	// debug
	if ( $this->debug ) $this->print_d('debugging is active for oid ' . $this->oid);
	if ( $this->debug ) $this->print_d('constructor complete for class ' . __CLASS__);
}
// END constructor

// destructor
function __destruct()
{
	if ( $this->debug ) $this->print_d("destroying class {$this->class_name} (oid: {$this->oid})");
}
// END destructor



/* ** PUBLIC METHODS ** */
// method: select account data from database (by domain)
function select_account_data_by_domain($domain)
{
  $_table = TBL_ACCT;
  
  $_sql = <<<SQL
SELECT uid, baffleticket
FROM $_table
WHERE domain = '$domain'
SQL;

  if ( $this->debug ) $this->print_d("attempting to select_account_data_by_domain(\$domain={$domain})");

  if ( !$DBOr = $this->DBO->Execute($_sql) )
  {
    trigger_error($this->DBO->ErrorMsg(), E_USER_WARNING);
    return 0;
  }
             
  // Check Result Set
  if ( !$DBOr->EOF )
  {
    $ROWS = $DBOr->GetArray();
    $this->account_baffleticket = $ROWS[0]['baffleticket'];
    $this->account_uid = $ROWS[0]['uid'];
  }
  else 
  {
    return 0;
  }

  if ( $this->debug ) $this->print_d("account_baffleticket for {$domain}: {$this->account_baffleticket}");
  
  return 1;
}
// END method

// method: get base domain from url
function get_base_domain($url)
{
  $base_domain = '';
  
  if ( $this->debug ) $this->print_d("get_base_domain for url: $url");
  
  // generic tlds (source: http://en.wikipedia.org/wiki/Generic_top-level_domain)
  $G_TLD = array(
    'biz','com','edu','gov','info','int','mil','name','net','org',
    'aero','asia','cat','coop','jobs','mobi','museum','pro','tel','travel',
    'arpa','root',
    'berlin','bzh','cym','gal','geo','kid','kids','lat','mail','nyc','post','sco','web','xxx',
    'nato',
    'example','invalid','localhost','test',
    'bitnet','csnet','ip','local','onion','uucp',
    'co'   // note: not technically, but used in things like co.uk
  );
  
  // country tlds (source: http://en.wikipedia.org/wiki/Country_code_top-level_domain)
  $C_TLD = array(
    // active
    'ac','ad','ae','af','ag','ai','al','am','an','ao','aq','ar','as','at','au','aw','ax','az',
    'ba','bb','bd','be','bf','bg','bh','bi','bj','bm','bn','bo','br','bs','bt','bw','by','bz',
    'ca','cc','cd','cf','cg','ch','ci','ck','cl','cm','cn','co','cr','cu','cv','cx','cy','cz',
    'de','dj','dk','dm','do','dz','ec','ee','eg','er','es','et','eu','fi','fj','fk','fm','fo',
    'fr','ga','gd','ge','gf','gg','gh','gi','gl','gm','gn','gp','gq','gr','gs','gt','gu','gw',
    'gy','hk','hm','hn','hr','ht','hu','id','ie','il','im','in','io','iq','ir','is','it','je',
    'jm','jo','jp','ke','kg','kh','ki','km','kn','kr','kw','ky','kz','la','lb','lc','li','lk',
    'lr','ls','lt','lu','lv','ly','ma','mc','md','mg','mh','mk','ml','mm','mn','mo','mp','mq',
    'mr','ms','mt','mu','mv','mw','mx','my','mz','na','nc','ne','nf','ng','ni','nl','no','np',
    'nr','nu','nz','om','pa','pe','pf','pg','ph','pk','pl','pn','pr','ps','pt','pw','py','qa',
    're','ro','ru','rw','sa','sb','sc','sd','se','sg','sh','si','sk','sl','sm','sn','sr','st',
    'sv','sy','sz','tc','td','tf','tg','th','tj','tk','tl','tm','tn','to','tr','tt','tv','tw',
    'tz','ua','ug','uk','us','uy','uz','va','vc','ve','vg','vi','vn','vu','wf','ws','ye','yu',
    'za','zm','zw',
    // inactive
    'eh','kp','me','rs','um','bv','gb','pm','sj','so','yt','su','tp','bu','cs','dd','zr'
    );
  
  
  // get domain
  if ( !$full_domain = $this->get_url_domain($url) )
  {
    return $base_domain;
  }
  
  // now the fun
  
    // break up domain, reverse
    $DOMAIN = explode('.', $full_domain);
    $DOMAIN = array_reverse($DOMAIN);
    if ( $this->debug ) $this->print_d('get_base_domain() -> DOMAIN: ' . $this->print_r($DOMAIN));
    
    // first check for ip address
    if ( count($DOMAIN) == 4 && is_numeric($DOMAIN[0]) && is_numeric($DOMAIN[3]) )
    {
      return $full_domain;
    }
    
    // if only 2 domain parts, that must be our domain
    if ( count($DOMAIN) <= 2 ) return $full_domain;
    
    // for explanation of logic, see: http://bafflegate.googlecode.com/svn/trunk/version0/functions.m02.inc.php
    if ( in_array($DOMAIN[0], $C_TLD) && in_array($DOMAIN[1], $G_TLD) && strtolower($DOMAIN[2]) != 'www' )
    {
      $base_domain = $DOMAIN[2] . '.' . $DOMAIN[1] . '.' . $DOMAIN[0];
    }
    else
    {
      $base_domain = $DOMAIN[1] . '.' . $DOMAIN[0];;
    }
  
  // did we succeed?  
  return $base_domain;
}
// END method

// method: get subdomain
function get_subdomain()
{
  $subdomain = '';
  if ( !$full_domain = $this->get_url_domain($url) ) return '';
  if ( !$base_domain = $this->get_base_domain($url) ) return '';
  $subdomain = str_replace($base_domain, '', $full_domain);
  if ( substr($subdomain,-1) == '.' ) $subdomain = substr($subdomain,0,strlen($subdomain)-1);
  return $subdomain;
}
// END method

// method: get url domain
function get_url_domain($url) 
{
  $domain = '';
  $_URL = parse_url($url);
  if ( !empty($_URL) && !empty($_URL['host']) ) $domain = $_URL['host'];
  return $domain;
} 
// END method

// method: get path (url - domain & qstring)
function get_path($url)
{
  $path = '';
  $_URL = parse_url($url);
  if ( !empty($_URL) && !empty($_URL['path']) ) $path = $_URL['path'];
  return $path;
}
// END method

// method: get query string
function get_query_string($url)
{
  $qstring = '';
  $delim_pos = strpos($url, '?');
  if ( $delim_pos === FALSE ) return ''; 
  $qstring = substr($url, $delim_pos + 1);
  
  // if query strings are separated by colons, change them to '&'
  if ( strpos($qstring,';') !== FALSE && strpos($qstring,'&') === FALSE ) $qstring = str_replace(';','&',$qstring);
  
  return $qstring;
}
// END method


// method: set exception
function set_exception($code, $message)
{
  $this->print_d("setting exception ($code): $message");
  $this->_EXCEPTION[] = array($code, $message);
}
// END method


// method: set exception
function has_exception()
{
  $has = 0;
  if ( !empty($this->_EXCEPTION) ) $has = 1;
  return $has;
}
// END method


// method: insert exceptions in DB
function insert_exception()
{
  $_sql = '';
  $_table = 'exception';
  
  if ( $this->debug ) $this->print_d('inserting exceptions into db');
  
  // do we have any exceptions?
  if ( empty($this->_EXCEPTION) ) return;
  
  foreach ( $this->_EXCEPTION as $_XDATA )
  {
    $_DATA_BIND[] = array($_XDATA[0],$this->visiting_url,$this->referrer_url,$this->referrer_domain,$_XDATA[1],$this->entrance_uts);
  }
  
  $_sql .= <<<SQL
INSERT INTO `$_table` 
( code, url, referrer_url, referrer_domain, message, uts ) 
VALUES ( ?,?,?,?,?,? )

SQL;
  
  if ( $this->debug ) $this->print_d("<pre>SQL:\n{$_sql}</pre>");
  
  // Run Query
  if ( !$DBO_r = $this->DBO->Execute($_sql,$_DATA_BIND) )
  {
    trigger_error($DBO->ErrorMsg(), E_USER_WARNING);
    return 0;
  }
  else
  {
    $insert_id = $this->DBO->Insert_ID();
    if ( empty($insert_id) )
    {
      trigger_error('insert successful, but no insert id -> setting to -1 to avoid error', E_USER_NOTICE);
      $insert_id = -1;
    }
    $this->_EXCEPTION = array();      // reset
  }
  
  return $insert_id;  
}
// END method


// method: print_d
function debug_dump($dirpath='')
{
  $_output = '';
  $_d1 = date('YmdHi');
  $_r = date('r');
  
  if ( empty($dirpath) ) $dirpath = $this->_log_dir;
  $fname = $this->class_name . ".debug.{$_d1}.txt";
  $fpath = $dirpath . $fname;
  
  if ( $this->debug ) $this->print_d("debug dump to file [$fname] (don't forget to clean out dir [$dirpath])");
  
  $i = 0;
  foreach ( $this->_DEBUG as $_msg ) 
  {
    $i++;
    $si = sprintf('%03s',$i);
    $_msg = strip_tags($_msg);
    $_output .= "[$si] $_msg\n";
  }
  
  // header
  $_header = <<<TXT
bafflegate
debug dump stack ($_r)


TXT;

  $this->write_file($_header.$_output, $fpath, $overwrite=1);
  return;
}
// END method

// method: write to file
function write_file($content, $fpath, $overwrite=1)
{
  $is_written = 0;
	$_MODE = array
	(
		'w_start' => 'w',			// overwrite
		'w_end' => 'a',				// append
	);

	// set mode
	$_mode = ( $overwrite ) ? $_MODE['w_start'] : $_MODE['w_end'] ;

	// open file (with lock?)
	if ( !$_handle = fopen($fpath, $_mode) )
	{
		trigger_error("unable to open file for writing [$fpath]", E_USER_WARNING);
		return 0;
	}
	
	// write
	if ( fwrite($_handle, $content) === FALSE )
	{
		trigger_error("unable to write to file [$fpath]", E_USER_WARNING);
		$is_written = 0;
	}
	else
	{
		$is_written = 1;
	}    
		
	fclose($_handle);
	return $is_written;
}
// END method

// method: print_d
function print_d($message, $color='#c33')
{
	$out = "<div style='line-height:1.5em; font-family:monospace; color:$color;'>$message</div>";
  $this->_DEBUG[] = $message;
  echo $out;
  return;
}
// END method

// method: print_r
function print_r($Mixed)
{
	$return = htmlspecialchars(print_r($Mixed, 1));
	$return = "<pre>$return</pre>";
	return $return;
}
// END method

// method: dump
function dump()
{
	echo $this->print_r($this);
	return;
}
// END method



/* ** PRIVATE METHODS ** */
// method: load db object
function _load_dbo()
{
  // already loaded
  if ( is_object($this->DBO) ) return;
  
  if ( $this->debug ) $this->print_d('loading db object');

  // load
  require_once($this->_dbo_driver);
  
  // settings
  $_flags = 2;
  $_dsn = "mysql://{$this->_dbu}:{$this->_dbp}@{$this->_dbs}/{$this->_dbn}?clientflags=$_flags";

  // create object
  if ( !$this->DBO =& ADONewConnection($_dsn) ) 
  {
    trigger_error('unable to connect to database ['.$db_name.']', E_USER_WARNING);
  }

  return;
}
// END method



// method: _set_session_data
function _set_session_data()
{
	// initialize session
	if ( !session_id() ) session_start(); 
	$_SESSION[$this->oid] = array();
	
	// add session data here
	
	return;
}
// END method

// method: get session data
function _get_session_data()
{
	// initialize session
	if ( !$this->_has_session_data() ) return; 
		
	// retrieve session variables
	// $this->var = $_SESSION[$this->oid]['var'];
	
	return;
}
// END method

// method: has session data
function _has_session_data()
{
	// initialize session
	if ( !session_id() ) session_start(); 
		
	// retrieve session variables
	if ( empty($_SESSION[$this->oid]) ) return 0;
	else return 1;
}
// END method

function _set_filename() { $this->_filename = basename(__FILE__); }
function _set_dirpath() { $this->_dirpath = dirname(__FILE__) . $this->DS; }

} // end class
/*____________________________________________________________________________*/

?>
